﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;

namespace MsExchangeBcsConnector.ExchangeBdcModel.Helpers
{
    public class SecurityAclUtils
    {
        /// <summary>
        /// Gets the security descriptor.
        /// </summary>
        /// <param name="userList">The user list.</param>
        /// <returns>returns SecurityDescriptor of groups</returns>
        public static byte[] GetNTAccountSecurityDescriptor(IEnumerable<NTAccount> userAccounts)
        {
            byte[] securityDescriptorBinary;
            SecurityIdentifier sid = null;
            CommonSecurityDescriptor sd = null;
            bool isSecurityDescriptorCreated = false;
            foreach (var userAccount in userAccounts)
            {
                try
                {
                    sid = (SecurityIdentifier)userAccount.Translate(typeof(SecurityIdentifier));
                    if (isSecurityDescriptorCreated == false)
                    {
                        sd = new CommonSecurityDescriptor(false, false, ControlFlags.None, sid, null, null, null);
                        sd.SetDiscretionaryAclProtection(true, false);

                        SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
                        sd.DiscretionaryAcl.RemoveAccess(AccessControlType.Allow, everyoneSid, unchecked((int)0xffffffffL), InheritanceFlags.None, PropagationFlags.None);
                        isSecurityDescriptorCreated = true;
                    }

                    sd.DiscretionaryAcl.AddAccess(AccessControlType.Allow, sid, unchecked((int)0xffffffffL), InheritanceFlags.None, PropagationFlags.None);
                }
                catch (Exception ex)
                {
                    LoggingService.LogError("Failed to generate security descriptor. Error: {0}", ex.Message);

                    throw ex;
                }
            }

            if (sd != null)
            {
                securityDescriptorBinary = new byte[sd.BinaryLength];
                sd.GetBinaryForm(securityDescriptorBinary, 0);
                return securityDescriptorBinary;
            }

            return null;
        }

        /// <summary>
        /// Encode a single claim to a byte array
        /// </summary>
        /// <param name="claimtype">The claim type to encode</param>
        /// <param name="claimvalue">The claim value to encode</param>
        /// <param name="claimissuer">The claim issuer to encode</param>
        /// <returns>A byte array with the encoded claim, return null if failure</returns>
        public static byte[] GetSecurityAclForClaim(bool isDeny, string claimtype, string claimvalue, string claimissuer)
        {
            Byte[] spAcl = null;
            if (!String.IsNullOrEmpty(claimtype) &&
                !String.IsNullOrEmpty(claimvalue) &&
                !String.IsNullOrEmpty(claimissuer))
            {
                using (var aclStream = new MemoryStream())
                {
                    var dest = new BinaryWriter(aclStream);
                    AddClaimAclToStream(dest, false, claimtype, claimvalue, claimissuer);
                    dest.Flush();
                    spAcl = aclStream.ToArray();
                }
            }

            return spAcl;
        }

        /// <summary>
        /// Add the claim to a binary stream. Invoke multiple times for all claims to add
        /// </summary>
        /// <param name="dest">The destination binary stream</param>
        /// <param name="isDeny">Specify if this is a deny or allow claim.</param>
        /// <param name="claimtype">The claim type</param>
        /// <param name="claimvalue">The claim value</param>
        /// <param name="claimissuer">The claim issuer</param>
        private static void AddClaimAclToStream(BinaryWriter dest, bool isDeny, string claimtype, string claimvalue, string claimissuer)
        {
            const string datatype = @"http://www.w3.org/2001/XMLSchema#string";

            if (string.IsNullOrEmpty(claimvalue))
            {
                return;
            }

            dest.Write(isDeny ? (byte)1 : (byte)0); // Allow = 0, Deny = 1
            dest.Write((byte)1); // Indicate that this is a non-NT claim type

            //
            // Claim Value
            //
            dest.Write((Int32)claimvalue.Length);
            dest.Write(Encoding.Unicode.GetBytes(claimvalue));

            //
            // Claim Type
            //
            dest.Write((Int32)claimtype.Length);
            dest.Write(Encoding.Unicode.GetBytes(claimtype));

            //
            // Claim Data Value Type
            //
            dest.Write((Int32)datatype.Length);
            dest.Write(Encoding.Unicode.GetBytes(datatype));

            //
            // Claim Original Issuer
            //
            dest.Write((Int32)claimissuer.Length);
            dest.Write(Encoding.Unicode.GetBytes(claimissuer));
        }
    }
}
